
package cn.org.rapid_framework.generator.util.paranamer;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cn.org.rapid_framework.generator.util.IOHelper;
import cn.org.rapid_framework.generator.util.StringHelper;

/**
 * get parameter names from java source file
 * @author badqiu
 */
public class JavaSourceParanamer implements Paranamer {

    private ClassLoader classLoader;

    public JavaSourceParanamer(ClassLoader classLoader) {

	if (classLoader==null)
	    throw new IllegalArgumentException("'classLoader' must be not null");
	this.classLoader = classLoader;
    }

    public String[] lookupParameterNames(AccessibleObject methodOrConstructor) {

	return lookupParameterNames(methodOrConstructor, true);
    }

    @SuppressWarnings("unchecked")
    public String[] lookupParameterNames(AccessibleObject methodOrConstructor,
	    boolean throwExceptionIfMissing) {

	try {
	    JavaSourceFileMethodParametersParser parser = new JavaSourceFileMethodParametersParser();
	    String javaSource = null;
	    if (methodOrConstructor instanceof Method) {
		Method m = (Method) methodOrConstructor;
		javaSource = m.getDeclaringClass().getName().replace('.', '/')+".java";
	    } else if (methodOrConstructor instanceof Constructor) {
		Constructor c = (Constructor) methodOrConstructor;
		javaSource = c.getDeclaringClass().getName().replace('.', '/')+".java";
	    } else {
		throw new IllegalArgumentException("unknow AccessibleObject"+methodOrConstructor
			+",must be Method or Constructor");
	    }

	    InputStream javaSourceInputStream = classLoader.getResourceAsStream(javaSource);
	    try {
		if (javaSourceInputStream!=null) {
		    return parser.parseJavaFileForParamNames(methodOrConstructor,
			    IOHelper.toString(javaSourceInputStream));
		}
		return Paranamer.EMPTY_NAMES;
	    } finally {
		if (javaSourceInputStream!=null)
		    javaSourceInputStream.close();
	    }
	} catch (IOException e) {
	    if (throwExceptionIfMissing) {
		throw new RuntimeException("IOException while reading javasource,method:"
			+methodOrConstructor, e);
	    } else {
		return Paranamer.EMPTY_NAMES;
	    }
	}
    }

    public static class JavaSourceFileMethodParametersParser {

	public String[] parseJavaFileForParamNames(Constructor<?> constructor, String content) {

	    return parseJavaFileForParamNames(content, constructor.getName(),
		    constructor.getParameterTypes());
	}

	public String[] parseJavaFileForParamNames(Method method, String content) {

	    return parseJavaFileForParamNames(content, method.getName(), method.getParameterTypes());
	}

	public String[] parseJavaFileForParamNames(AccessibleObject methodOrConstructor,
		String content) {

	    if (methodOrConstructor instanceof Method) {
		return parseJavaFileForParamNames((Method) methodOrConstructor, content);
	    } else if (methodOrConstructor instanceof Constructor) {
		return parseJavaFileForParamNames((Constructor) methodOrConstructor, content);
	    } else {
		throw new IllegalArgumentException("unknow AccessibleObject"+methodOrConstructor
			+",must be Method or Constructor");
	    }
	}

	private String[] parseJavaFileForParamNames(String content, String name,
		Class<?>[] parameterTypes) {

	    Pattern methodPattern = Pattern.compile("(?s)"+name+"\\s*\\("
		    +getParamsPattern(parameterTypes)+"\\)\\s*\\{");
	    Matcher m = methodPattern.matcher(content);
	    List<String> paramNames = new ArrayList<String>();
	    while (m.find()) {
		for (int i = 1; i<=parameterTypes.length; i++) {
		    paramNames.add(m.group(i));
		}
		return (String[]) paramNames.toArray(new String[0]);
	    }
	    return null;
	}

	private String getParamsPattern(Class<?>[] parameterTypes) {

	    List paramPatterns = new ArrayList();
	    for (int i = 0; i<parameterTypes.length; i++) {
		Class type = parameterTypes[i];
		String paramPattern = ".*"+type.getSimpleName()+".*\\s+(\\w+).*";
		paramPatterns.add(paramPattern);
	    }
	    return StringHelper.join(paramPatterns, ",");
	}

    }

}
